﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace FluentETL.Impl
{
    public class TextDestination : IDestination
    {
        public class RowItem
        {
            private string _name;
            private object _value;

            public string Name
            {
                get { return _name; }
            }

            public object Value
            {
                get { return _value; }
            }

            public RowItem(string name, object value)
            {
                _name = name;
                _value = value;
            }
        }

        private TextWriter OutputWriter { get; set; }
        private string OutputFile { get; set; }
        private bool WriteHeaderFlag { get; set; }
        private IEnumerable<string> FieldNames { get; set; }
        private Func<IRow, string> WriteLine { get; set; }
        private TextWriter _activeWriter;

        public TextDestination To(TextWriter writer)
        {
            this.OutputWriter = writer;
            return this;
        }

        public TextDestination To(string file)
        {
            this.OutputFile = file;
            return this;
        }

        public TextDestination WithHeader(bool b)
        {
            this.WriteHeaderFlag = b;
            return this;
        }

        public TextDestination SetFieldNames(IEnumerable<string> fieldNames)
        {
            this.FieldNames = fieldNames;
            return this;
        }

        public TextDestination WriteAsFixedColumns(int[] columnWidths, string separator)
        {
            return this.WriteAs(rowItems =>
            {
                IList<string> parts = new List<string>();
                int ctr = 0;
                foreach (var item in rowItems)
                {
                    parts.Add((item.Value ?? "").ToString().PadRight(columnWidths[ctr++]));
                }
                return string.Join(separator, parts.ToArray());
            });
        }

        public TextDestination WriteAsFixedColumns(int columnWidth, string separator)
        {
            return this.WriteAs(rowItems =>
            {
                IList<string> parts = new List<string>();
                foreach (var item in rowItems)
                {
                    parts.Add((item.Value ?? "").ToString().PadRight(columnWidth));
                }
                return string.Join(separator, parts.ToArray());
            });
        }

        public TextDestination WriteAsDelimited(string separator)
        {
            this.WriteLine = (row) =>
            {
                IList<string> parts = new List<string>();
                foreach (var item in EnumOrderedRowItems(row))
                {
                    parts.Add((item.Value ?? "").ToString());
                }
                return string.Join(separator, parts.ToArray());
            };
            return this;
        }

        public TextDestination WriteAs(Func<IEnumerable<RowItem>, string> funcFormatter)
        {
            this.WriteLine = (row) =>
            {
                return funcFormatter(EnumOrderedRowItems(row));
            };
            return this;
        }

        #region IDestination Members

        void IDestination.InTransformContext(ITransform t, Action action)
        {
            if (this.FieldNames == null)
            {
                this.FieldNames = t.GetMappedDestinationFields();
            }

            Action _checkAndWriteHeader = () =>
            {
                if (this.WriteHeaderFlag)
                {
                    DictionaryRow headerRow = new DictionaryRow();
                    foreach (string field in this.FieldNames)
                        headerRow[field] = field;
                    _activeWriter.WriteLine(this.WriteLine(headerRow));
                }
            };

            if (this.OutputWriter != null)
            {
                _activeWriter = this.OutputWriter;
                _checkAndWriteHeader();
                action();
                _activeWriter = null;
            }
            else if (this.OutputFile != null)
            {
                using (StreamWriter wrtr = new StreamWriter(this.OutputFile))
                {
                    _checkAndWriteHeader();
                    _activeWriter = wrtr;
                    action();
                    _activeWriter = null;
                }
            }
            else
                throw new InvalidOperationException("No file/stream set.");
        }

        void IDestination.Process(IRow row)
        {
            string line = this.WriteLine(GetArrangedRow(row));
            _activeWriter.WriteLine(line);
        }

        void IDestination.Insert(IRow row)
        {
            ((IDestination)this).Process(row);
        }

        void IDestination.Update(IRow row)
        {
            ((IDestination)this).Process(row);
        }

        void IDestination.Delete(IRow row)
        {
            ((IDestination)this).Process(row);
        }

        #endregion

        private IEnumerable<RowItem> EnumOrderedRowItems(IRow row)
        {
            foreach (string field in this.FieldNames)
            {
                yield return new RowItem(field, row[field]);
            }
        }

        private IRow GetArrangedRow(IRow row)
        {
            DictionaryRow newRow = new DictionaryRow();
            foreach (string field in this.FieldNames)
                newRow[field] = row[field];
            return newRow;
        }
    }
}